package schema.ext;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import jhunter.annotation.Inject;
import jhunter.common.exception.ConfigException;
import jhunter.service.Service;
import jhunter.web.mvc.ActionContext;
import jhunter.web.mvc.controller.Controller;

import org.apache.commons.io.FilenameUtils;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.permission.WildcardPermission;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;

import schema.Environment;
import schema.vo.module.Action;

public class ActionExt {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	private Action action;

	private ModuleExt moduleExt;

	private PathInfo pathInfo;

	private String contextPath;

	private Map<String, Method> methodMap = new HashMap<String, Method>();

	public ActionExt(ModuleExt moduleExt, Action action) {
		this.action = action;
		this.moduleExt = moduleExt;
		pathInfo = new PathInfo(action.getPath(), HttpMethod.getByString(action.getHttpMethod()));

		String contextPath = action.getPath();

		if (contextPath.startsWith("/")) {
			contextPath = contextPath.substring(1);
		}
		if (contextPath.endsWith("/")) {
			contextPath = contextPath.substring(0, contextPath.length());
		}
		this.contextPath = contextPath;
	}

	/**
	 * product model used
	 */
	private Class<?> classCache;

	private Class<?> classCacheDev;

	protected Class<?> getControllerClass() {
		Class<?> clazz = null;
		if (Environment.isDev()) {
			if (classCacheDev == null) {
				try {
					ClassLoader classLoader = moduleExt.getInstanceClassLoader();
					clazz = classLoader.loadClass(action.getClazz());
					classCacheDev = clazz;

					Method[] list = classCacheDev.getMethods();
					for (Method method : list) {
						methodMap.put(method.getName(), method);
					}

				} catch (ClassNotFoundException e) {
					e.printStackTrace();
					throw new ConfigException(e);
				}
			} else {
				return classCacheDev;
			}
		} else {
			if (classCache != null) {
				clazz = classCache;
			} else {
				try {
					clazz = Class.forName(action.getClazz());
					classCache = clazz;
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
					throw new ConfigException(e);
				}
			}
		}
		return clazz;
	}

	public Controller getControllerInstance() {
		try {
			Class<?> clazz = getControllerClass();
			final Controller controller = (Controller) clazz.newInstance();

			ReflectionUtils.doWithFields(clazz, new FieldCallback() {
				@Override
				public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
					Inject inject = field.getAnnotation(Inject.class);
					if (inject != null) {
						Class<?> clazz = field.getType();
						ModuleExt module = ActionExt.this.moduleExt;
						Service service = (Service) module.getServiceMap().get(clazz.getName());
						if (Service.class.isAssignableFrom(clazz)) {
							if (!field.isAccessible()) {
								field.setAccessible(true);
							}
							if (inject.name() != null && !inject.name().equals("")) {
								service = (Service) module.getServiceMap().get(inject.name());
							}
							field.set(controller, service);
						}
					}
				}
			});
			return controller;
		} catch (InstantiationException e) {
			e.printStackTrace();
			throw new ConfigException(e);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			throw new ConfigException(e);
		}

	}

	public Object invoke(ActionContext context) {
		Controller controller = getControllerInstance();

		String methodName = this.action.getMethod();
		Method method = methodMap.get(methodName);
		if (method == null) {
			throw new RuntimeException("no method named:" + action.getMethod() + " in class[" + action.getClazz() + "]");
		}

		Class<?>[] argTypes = method.getParameterTypes();
		Object[] args = new Object[argTypes.length];
		for (int i = 0; i < argTypes.length; i++) {
			Class<?> type = argTypes[i];
			if (!type.equals(ActionContext.class)) {
				throw new RuntimeException("type must be ActionContext");
			}
			args[i] = ActionContext.getContext();
		}
		try {
			return method.invoke(controller, args);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		} catch (InvocationTargetException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}

	public void renderTemplate(String r, HttpServletRequest request, HttpServletResponse response) throws ResourceNotFoundException,
			ParseErrorException, MethodInvocationException, IOException {
		String basePath = new File(moduleExt.getFile()).getParent() ;
		String templateFile =  basePath + "/view/" + r + ".vm";
		templateFile = FilenameUtils.separatorsToUnix(templateFile);
		logger.debug("render template : {}", templateFile);
		AppExt app = (AppExt) moduleExt.getParent();
		Template template = app.getAppContext().getVelocityEngine().getTemplate(templateFile, "utf-8");

		ActionContext context = ActionContext.getContext();
		VelocityContext values = new VelocityContext();
		values.put("params", context.getParameters());
		values.put("session", context.getSession());
		values.put("application", context.getApplication());
		values.put("flash", context.getFlash());
		values.put("contextPath", request.getContextPath());
		values.put("libPath", app.getAppContext().getViewLibPath());
		values.put("basePath", app.getContextPath() + "/" + moduleExt.getContextPath());
		Map<String, Object> atts = context.getAttributes();
		if (atts != null) {
			for (String key : atts.keySet()) {
				values.put(key, atts.get(key));
			}
		}
		template.merge(values, response.getWriter());
	}

	public Permission getPermission() {
		AppExt app = (AppExt) moduleExt.getParent();
		/**
		 * appid:moduleid:actionName
		 */
		String p = "action:" + app.getApp().getId() + ":" + moduleExt.getModule().getId() + ":" + this.action.getName();
		WildcardPermission permission = new WildcardPermission(p);
		return permission;
	}

	public Action getAction() {
		return action;
	}

	public void setAction(Action action) {
		this.action = action;
	}

	public ModuleExt getModuleExt() {
		return moduleExt;
	}

	public void setModuleExt(ModuleExt moduleExt) {
		this.moduleExt = moduleExt;
	}

	public String getExtendTemplateFilePath() {
		return null;
	}

	public String getAppendTemplateFilePath() {

		return null;
	}

	public String getTemplateFile(String name) {

		return null;
	}

	public PathInfo getPathInfo() {
		return pathInfo;
	}

	public String getContextPath() {
		return contextPath;
	}

}
